/**
 * 
 */
package com.swg.cocobox.server;

import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.swg.cocobox.KeyChange;

/**
 * @author satriaprayoga
 *
 */
public abstract class ReadWriteServer extends ReaderServer{
	
	protected Map<SocketChannel, List<ByteBuffer>> pendingData;

	public ReadWriteServer(InetAddress address, int port) throws IOException {
		super(address, port);
		pendingData=new HashMap<SocketChannel, List<ByteBuffer>>();
	}

	public ReadWriteServer(String host, int port) throws IOException {
		this(InetAddress.getByName(host),port);
	}
	
	public void send(SocketChannel channel,byte[] data)throws IOException{
		synchronized (keyChanges) {
			changeKey(channel, SelectionKey.OP_WRITE);
			synchronized (pendingData) {
				List<ByteBuffer> queue=pendingData.get(channel);
				if(queue==null){
					queue=new ArrayList<ByteBuffer>();
					pendingData.put(channel, queue);
				}
				queue.add(ByteBuffer.wrap(data));
			}
		}
		selector.wakeup();
	}
	
	@Override
	protected void accept(SelectionKey key) throws IOException {
		ServerSocketChannel channel=(ServerSocketChannel)key.channel();
		SocketChannel socketChannel=channel.accept();
		socketChannel.configureBlocking(false);
		socketChannel.register(selector, SelectionKey.OP_READ);
		
	}
	
	protected void write(SelectionKey key)throws IOException{
		SocketChannel channel=(SocketChannel)key.channel();
		synchronized (pendingData) {
			List<ByteBuffer> queue=pendingData.get(channel);
			while(!queue.isEmpty()){
				ByteBuffer buffer=queue.get(0);
				channel.write(buffer);
				if(buffer.remaining()>0){
					break;
				}
				queue.remove(0);
			}
			if(queue.isEmpty()){
				key.interestOps(SelectionKey.OP_READ);
			}
		}
	}
	
	@Override
	public void run() {
		while(true){
			try {
				synchronized (keyChanges) {
					Iterator<KeyChange> keyIterator=keyChanges.iterator();
					while(keyIterator.hasNext()){
						KeyChange kc=keyIterator.next();
						switch (kc.getType()) {
						case KeyChange.CHANGE:
							SelectionKey key=kc.getChannel().keyFor(selector);
							key.interestOps(kc.getOperation());
						}
					}
					keyChanges.clear();
				}
				selector.select();
				Iterator<SelectionKey> selectedKeys=selector.selectedKeys().iterator();
				while(selectedKeys.hasNext()){
					SelectionKey key=selectedKeys.next();
					selectedKeys.remove();
					if(!key.isValid()){
						continue;
					}
					if(key.isAcceptable()){
						accept(key);
					}else if(key.isReadable()){
						read(key);
					}else if(key.isWritable()){
						write(key);
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}

}
